package vrp;

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;

import java.util.Arrays;
import java.util.Date;
import java.util.Calendar;
import java.util.List;
import java.util.ArrayList;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.text.ParseException;
import java.io.IOException;
import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import javax.servlet.http.*;

import vrp.PMF;
import vrp.Engine;
import vrp.Planning;
import vrp.Route;
import vrp.Bundle;
import vrp.Delivery;
import vrp.Customer;
import vrp.Product;
import vrp.Vehicle;
import vrp.Distance;

public class PlanningAddServlet extends HttpServlet {

    public void doPost(HttpServletRequest req, HttpServletResponse resp)
                throws IOException {

    	PersistenceManager pm = PMF.get().getPersistenceManager();
    	DateFormat df = new SimpleDateFormat ("yyyy-MM-dd");
    	try {
    		//get POST data
        	Date date = df.parse(req.getParameter("date"));
        	int algorithm = Integer.parseInt(req.getParameter("algorithm"));
        	int myDistanceType = Integer.parseInt(req.getParameter("distanceType"));
        	
        	//get List of Deliveries
            Query query = pm.newQuery(Delivery.class);
            query.setFilter("date == datepara");
            query.setFilter("status == 0");
            query.setOrdering("customer asc");
            query.declareParameters("String datepara");
            List<Delivery> deliveries = (List<Delivery>) query.execute(date);
        	int deliveriesSize = deliveries.size();

        	if (deliveriesSize==0) {
        		pm.close();
        		resp.sendRedirect("/plannings.jsp?info=nodelivery");
        		return;
        	}

            //internal data structure
        	int i, j;
            ArrayList<Key> customerKeys = new ArrayList<Key>();
            ArrayList<Long> customerIDs = new ArrayList<Long>();
            ArrayList<Double> demands = new ArrayList<Double>();
        	
        	//get depot
            query = pm.newQuery(Customer.class);
            query.setFilter("isDepot == true");
            List<Customer> depots = (List<Customer>) query.execute();
            int distanceType = 0;
        	Customer depot = depots.get(0);
            customerKeys.add(depot.getKey());
            customerIDs.add(depot.getKey().getId());
            demands.add(new Double(0));

            //get other customerKeys
            Key customerKey = deliveries.get(0).getCustomer();
            customerKeys.add(customerKey);
            customerIDs.add(customerKey.getId());
            double demand = (pm.getObjectById(Product.class, deliveries.get(0).getProduct()).getWeight())*(deliveries.get(0).getQuantity());
        	for (i=1; i<deliveriesSize; i++) {
        		Delivery delivery = deliveries.get(i);
        		Key newCustomerKey = delivery.getCustomer();
        		Key productKey = delivery.getProduct();
        		Product product = pm.getObjectById(Product.class, productKey);
        		double weight = product.getWeight();
        		int quantity = delivery.getQuantity();
        		double newDemand = weight*quantity;
        		
        		if (customerKey.equals(newCustomerKey)) {
        			demand += newDemand;
        		} else {
        			demands.add(new Double(demand));
        			demand = newDemand;
        			customerKeys.add(newCustomerKey);
        			customerIDs.add(newCustomerKey.getId());
        			customerKey = newCustomerKey;
        		}
        	}
			demands.add(new Double(demand));
        	
            //get distances
			int numCustomers = customerIDs.size();
            double[] newDemands = new double[numCustomers];
        	double[][] distances = new double[numCustomers][numCustomers];
       	
        	for (i=0;i<numCustomers;i++){
        		newDemands[i] = demands.get(i).doubleValue();
        		distances[i][i] = 0;
        	}

    		//populate distances
        	switch (distanceType) {
        	case 0: //driving
        		query = pm.newQuery(Distance.class);
                List<Distance> queryDistances = (List<Distance>) query.execute();

                for (i=0;i<queryDistances.size();i++){
                	Distance queryDistance = queryDistances.get(i);
                	int fromCustomerIndex = customerIDs.indexOf(new Long(queryDistance.getFrom()));
                	int toCustomerIndex = customerIDs.indexOf(new Long(queryDistance.getTo()));
                	if (fromCustomerIndex>=0 && toCustomerIndex>=0) {
                		distances[fromCustomerIndex][toCustomerIndex] = queryDistance.getLength();
                	}
                }
        		break;
        	case 1: //straight-line
        		query = pm.newQuery(Customer.class);
                List<Customer> queryCustomers = (List<Customer>) query.execute();

                for (i=0;i<queryCustomers.size();i++){
                	Customer fromCustomer = queryCustomers.get(i);
                	int fromCustomerIndex = customerIDs.indexOf(new Long(fromCustomer.getKey().getId()));
                	if (fromCustomerIndex>=0) {
                		for (j=0;j<queryCustomers.size();j++){
                        	Customer toCustomer = queryCustomers.get(j);
                        	int toCustomerIndex = customerIDs.indexOf(new Long(toCustomer.getKey().getId()));
                        	if (toCustomerIndex>=0 && toCustomerIndex != fromCustomerIndex) {
                        		distances[fromCustomerIndex][toCustomerIndex] = StraightLineDistance(Math.toRadians(fromCustomer.getLat()),Math.toRadians(fromCustomer.getLng()),Math.toRadians(toCustomer.getLat()),Math.toRadians(toCustomer.getLng()));
                        	}
                		}
                	}
                }
        		break;
        	}
			
            //call algorithm
            double vehicleCapacity = 100;
            query = pm.newQuery(Vehicle.class);
            List<Vehicle> vehicles = (List<Vehicle>) query.execute();
            if (!vehicles.isEmpty()) {
            	//just get the capacity of the first vehicle - assume all equal - to be expanded
            	vehicleCapacity = vehicles.get(0).getCapacity(); 
            }
            Engine engine = new Engine(vehicleCapacity, newDemands, distances);
            switch (algorithm) {
            case 0:
            	engine.SavingHeuristics();
            	break;
            case 1:
            	engine.GRASPMetaHeuristics();
            	break;
            }
            
            //call constructor
            double totalDistance = engine.getTotalDistance();
            long totalTime = engine.getTotalTime();
            ArrayList<ArrayList<Integer>> indexRoutes = engine.getRoutes();
            ArrayList<Route> routes = new ArrayList<Route>();
            for (i=0;i<indexRoutes.size();i++){
            	ArrayList<Key> routeCustomers = new ArrayList<Key>();
            	ArrayList<Bundle> routeBundles = new ArrayList<Bundle>();
            	for (j=0;j<indexRoutes.get(i).size();j++){
            		Key routeCustomer = customerKeys.get(indexRoutes.get(i).get(j).intValue()); 
            		routeCustomers.add(routeCustomer);
                	ArrayList<Key> routeProducts = new ArrayList<Key>();
                	ArrayList<Integer> routeQuantities = new ArrayList<Integer>();
            		for (Delivery delivery : deliveries) {
            			if (delivery.getCustomer().equals(routeCustomer)){
            				routeProducts.add(delivery.getProduct());
            				routeQuantities.add(new Integer(delivery.getQuantity()));
            			}
            		}
            		Bundle bundle = new Bundle(routeProducts,routeQuantities);
            		routeBundles.add(bundle);
            	}
            	Route route = new Route(routeCustomers, routeBundles);
            	routes.add(route);
            }
            Planning planning = new Planning(date, algorithm, myDistanceType, totalDistance, totalTime, routes); 
            pm.makePersistent(planning);
        } catch (ParseException e) {
			e.printStackTrace();
		} finally {
            pm.close();
        }
        resp.sendRedirect("/plannings.jsp");
    }
    
    public double StraightLineDistance(double lat1, double lng1, double lat2, double lng2){
    	double EARTHRADIUS = 6371000;
    	double dLat = lat2-lat1;
    	double dLon = lng2-lng1; 
    	double a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.cos(lat1) * Math.cos(lat2) * Math.sin(dLon/2) * Math.sin(dLon/2); 
    	double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); 
    	double d = EARTHRADIUS * c;
    	return d;
    }
}